package com.letoken.platform.repo.utlis.lock;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.letoken.platform.repo.app.redis.RedisConfig;
import com.letoken.platform.repo.utlis.RedisClient;
import com.letoken.platform.repo.utlis.WebUtils;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;

import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;

/**
 * @Date 2018/4/17 21:16
 * @Created GG
 * @Description
 */
@Slf4j
@Aspect
@Order(-100)
@Component
public class LockAspect implements ApplicationContextAware {



    private static String prefix = "LOCK:";


    @Resource
    private RedisConfig redisConfig;



    @Pointcut("@annotation(com.letoken.platform.repo.utlis.lock.Lock)")
    public void method() {
    }


    @Around(value = "method() && @annotation(valueAnnotation)")
    public Object around(ProceedingJoinPoint point, Lock valueAnnotation) throws Throwable {
        String name = valueAnnotation.value();
        if (!StringUtils.hasLength(name)){
            MethodSignature signature = (MethodSignature) point.getSignature();
            name = signature.getName().toUpperCase();
        }
        LockCategoryEnum categoryEnum = valueAnnotation.ce();
        String key = "";
        if (categoryEnum == LockCategoryEnum.USER_ID){
            Integer userId = WebUtils.getUserId();
            if (!ObjectUtils.isEmpty(userId))
                key = prefix + name + ":" + userId;

        }else if (categoryEnum == LockCategoryEnum.ID){
            JSONObject jo = JSONObject.parseObject(JSON.toJSONString(point.getArgs()[0]));
            Long id = jo.getLongValue(valueAnnotation.field());
            if (!ObjectUtils.isEmpty(id) && id > 0)
                key = prefix + name + ":" + id;
        }else if (categoryEnum == LockCategoryEnum.DEFAULT){
            key = prefix + name;
        }
        if (!StringUtils.hasLength(key)){
            log.info("加锁失败 无法识别锁");
            throw new RuntimeException("操作失败!");
        }

        long expire = Long.valueOf(valueAnnotation.expire());
        TimeUnit unit = valueAnnotation.unit();
        RedissonClient redissonClient = redisConfig.getRedisson();
        RLock rLock = null;
        try {
            rLock = lock(key,expire,unit);
            Object obj = point.proceed(point.getArgs());
            return obj;
        } finally {
            if (rLock !=null && rLock.isHeldByCurrentThread()){
                rLock.unlock();
            }
        }




    }

    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        // 避免不同的项目使用同一个redis桶的时候，标识冲突
        String san = applicationContext.getEnvironment().getProperty("spring.application.name");
        if (StringUtils.hasLength(san)){
            prefix = "LOCK:" +  san.toUpperCase() + ":";
        }
    }

    public RLock lock(final String key, final Long l,final TimeUnit timeUnit){
        RedissonClient redissonClient = redisConfig.getRedisson();
        RLock rLock = redissonClient.getLock(key);
        rLock.lock(l,timeUnit);
        return rLock;
    }

}
